/*
 DNN Content Builder - sourceforge.net/projects/dnncb
 Copyright (c) 2004-2008
 by Emerson Lopes (emersonlopes@gmail.com)

 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
 documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
 the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
 to permit persons to whom the Software is furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all copies or substantial portions 
 of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
 TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 DEALINGS IN THE SOFTWARE.
*/

using DotNetNuke.Common.Utilities;
using DNNContentBuilder.UserControls.SearchFieldControls;

namespace DNNContentBuilder.UserControls.FieldControls
{
	using System;
	using System.Collections;
	using System.Data;
	using System.IO;
	using System.Text;
	using System.Web;
	using System.Web.UI.WebControls;
	using System.Xml;
	using DNNContentBuilder.Attributes;

	/// <summary>
	///		Summary description for DropDownControl.
	/// </summary>
	[FormEngineSearchControl(typeof(TextSearchControl))]
	public class LinkerControl : System.Web.UI.UserControl, IFormlet
	{
		#region Private Classes
		internal new class  Adapter
		{
			private FieldDefinitionList container;

			public Adapter(FieldDefinitionList container)
			{
				this.container = container;
			}

			public static DataTable GetDataTable(string xml)
			{
				DataSet ds = new DataSet();
				
				ds.ReadXml(new StringReader(xml), XmlReadMode.IgnoreSchema);

				return ds.Tables[0];
			}

			public static string GetXml(DataTable dataSource)
			{
				DataSet ds = new DataSet();
				StringBuilder sb = new StringBuilder();

				ds.Tables.Add(dataSource);
				
				using (System.IO.StringWriter writer = new System.IO.StringWriter(sb))
				{
					ds.WriteXml(writer);
				}

				return sb.ToString();
			}

			public static Hashtable GetFormlets(DataTable dataSource)
			{
				Hashtable result = new Hashtable();

				for (int i = 0; i < dataSource.Rows.Count; i++)
				{
					DataRow dr = dataSource.Rows[i];

					foreach (DataColumn dc in dataSource.Columns)
					{
						// Ignore reserved columns
						if (dc.ColumnName == LinkerControl.IDColumnName || 
							dc.ColumnName == LinkerControl.TitleColumnName)
						{
							continue;
						}

						// TODO: derive proper type
						object value = dr[dc.ColumnName];

						DummyFormlet formlet = new DummyFormlet(value, value.GetType());

						// TODO: Use LinkerControl.GetValue() instead. Ugly uniqueness
						string key = String.Format("{0};{1}", dc.ColumnName, i);

						result.Add(key, formlet);
					}
				}

				return result;
			}

			private static void AddColumn(DataTable dt, string columnName)
			{
				if (!dt.Columns.Contains(columnName))
				{
					dt.Columns.Add(columnName, typeof(string));
				}
			}

			public DataTable GetTableSchema(DataTable dt)
			{
				return Adapter.GetTableSchema(dt, container.FormDefinitionId, container.TableName);
			}

			public static DataTable GetTableSchema(DataTable dt, int formDefinitionId, string tableName)
			{
				AddColumn(dt, LinkerControl.IDColumnName);
				AddColumn(dt, LinkerControl.TitleColumnName);

				ArrayList fields = new DbContentController(formDefinitionId).GetFields(tableName);

				foreach (FieldDefinitionInfo fdInfo in fields)
				{
					AddColumn(dt, fdInfo.SourceField);

					if (fdInfo.IsPrimaryKey)
					{
						dt.Columns[LinkerControl.IDColumnName].Expression = String.Format("{0}", fdInfo.SourceField);
					}

					if (fdInfo.IsTitle)
					{
						dt.Columns[LinkerControl.TitleColumnName].Expression = String.Format("{0}", fdInfo.SourceField);
					}
				}

				return dt;
			}

			public DataTable Merge(DataTable source)
			{
				DataTable result = new DataTable();

				if (source != null && source.Rows.Count > 0)
				{
					result = source.Copy();
				}

				result = this.GetTableSchema(result);

				string filterExpression = String.Format("{0} = '{1}'", 
					LinkerControl.IDColumnName, container.PrimaryKeyValue);
				
				DataRow[] rows = result.Select(filterExpression);
				
				bool createNewRow = (rows.Length == 0);

				// Select the first row or create a new one if not found
				DataRow row = createNewRow ? result.NewRow() : rows[0];

				ArrayList fields = new DbContentController(container.FormDefinitionId).GetFields(container.TableName);
				
				// Set the value of the primary key field
				FieldDefinitionInfo pkInfo = FormEngine.GetPrimaryKeyField(fields);
				object pkValue = container.PrimaryKeyValue;
				
				// Added support for SQL Server Guid columns
				if (row.Table.Columns[pkInfo.SourceField].DataType == typeof(Guid))
				{
					pkValue = new Guid(container.PrimaryKeyValue);
				}
				
				row[pkInfo.SourceField] = pkValue;

				// Set the values of the remaining fields
				foreach (DictionaryEntry entry in container.FieldDictionary)
				{
					string columnName  = (string)entry.Key;
					string columnValue = (string)entry.Value;

					columnValue = (string)TypeEvaluator.GetFieldValue(columnValue, columnName);

					row[columnName] = columnValue;
				}

				if (createNewRow)
				{
					result.Rows.Add(row);
				}

				return result;
			}
		}
		#endregion

		#region Constants
		public const string IDColumnName = "epcbID";
		public const string TitleColumnName = "epcbTitle";
		public const string SessionKey = "DNNContentBuilder.LinkerControl.LinkerDataSource";
		#endregion

		#region Member Variables
		private bool isValid = true;

		private XmlDocument xmlDoc = new XmlDocument();
		private XmlNode xmlNode;
		#endregion

		#region Controls
		protected System.Web.UI.WebControls.DataList dlCustomTable;
		protected DNNContentBuilder.UI.WebControls.DummyPostBackHandler PostBackHandler;
		#endregion

		#region Properties
		/// <summary>
		/// Wrapper for <see cref="FormEngineChildControl.ResultOk"/>
		/// </summary>
		public string ResultOk
		{
			get
			{
				return FormEngineChildControl.ResultOk;
			}
		}

		/// <summary>
		/// TODO: document
		/// </summary>
		public DataTable DataSource
		{
			get
			{
				object savedState = this.Session[SessionKey];

				if (savedState != null)
				{
					return (DataTable)savedState;
				}

				return null;
			}
			set
			{
				this.Session[SessionKey] = value;
			}
		}

		/// <summary>
		/// Gets or sets the Xml that represents the configuration data for this module
		/// </summary>
		protected string Xml
		{
			get
			{
				return (string)this.ViewState["Xml"];
			}
			set
			{
				this.ViewState["Xml"] = value;
			}
		}

		public string ForeignKeyValue
		{
			get
			{
				return (string)this.ViewState["ForeignKeyValue"];
			}
			set
			{
				this.ViewState["ForeignKeyValue"] = value;
			}
		}

		/// <summary>
		/// Gets or sets the field definition for the linker.
		/// </summary>
		public FieldDefinitionInfo FieldDefinition
		{
			get
			{
				object savedState = this.ViewState["FieldDefinition"];

				if (savedState != null)
				{
					return (FieldDefinitionInfo)savedState;
				}

				return null;
			}
			set
			{
				this.ViewState["FieldDefinition"] = value;
			}
		}

		/// <summary>
		/// Gets or sets the name of the table the link is enumerating
		/// </summary>
		protected string TableName
		{
			get
			{
				if (this.FieldDefinition != null)
				{
					return this.FieldDefinition.SourceField.Split('.')[0];
				}

				return String.Empty;
			}
		}

		// TODO: remove unneeded entries from ResourceEngine.resx
//		protected string TableName
//		{
//			get
//			{
//				if (tableName == null && this.Xml != null)
//				{
//					XmlDocument xd = new XmlDocument();
//
//					xd.LoadXml(this.Xml);
//					XmlNode node = xd.SelectSingleNode("//LinkedField");
//
//					if (node == null)
//					{
//						throw new ApplicationException(ResourceEngine.GetString("NO_LINKED_FIELD_NODE"));
//					}
//					
//					string fieldName = node.InnerXml;
//
//					if (fieldName == null || fieldName.IndexOf('.') < 0)
//					{
//						throw new ApplicationException(ResourceEngine.GetString("MALFORMED_FIELD_NAME"));
//					}
//
//					tableName = fieldName.Split('.')[0];
//				}
//
//				return tableName;
//			}
//		}
		
		public string PostBackEventReference
		{
			get
			{
				return Page.GetPostBackEventReference(this.PostBackHandler, String.Empty);
			}
		}
		#endregion

		#region Event Hanlders
		private void Page_Load(object sender, System.EventArgs e)
		{
			if (!this.IsPostBack && !IsPopup(this.NamingContainer))
			{
//				LoadData();
			}

			this.DataBind();
		}

		/// <summary>
		/// Load linked data from the database
		/// </summary>
		public void LoadData()
		{
			ArgumentValidator.CheckForNullReference(this.FieldDefinition, "FieldDefinition");
			ArgumentValidator.CheckForNullReference(this.ForeignKeyValue, "ForeignKeyValue");

			LinkerController controller = new LinkerController(this.FieldDefinition, this.ForeignKeyValue);
			this.DataSource = controller.GetByLinkedTable();
		}

		public void Clear()
		{
			this.DataSource = null;
			this.DataBind();
		}

		private bool IsPopup(System.Web.UI.Control container)
		{
			if (container == null)
			{
				return false;
			}

			if (container is IPopupContainer)
			{
				return ((IPopupContainer)container).PopupMode;
			}

			return IsPopup(container.NamingContainer);
		}

		private void dlCustomTable_ItemCreated(object sender, System.Web.UI.WebControls.DataListItemEventArgs e)
		{
			if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
			{
				// Add handler to the delete item button
				ImageButton deleteButton = (ImageButton)e.Item.FindControl("btDeleteRow");
				
				if (deleteButton != null)
				{
					deleteButton.Command += new CommandEventHandler(this.deleteButton_Command);
				}
			}
		}

		private void deleteButton_Command(object sender, System.Web.UI.WebControls.CommandEventArgs e)
		{
			DataTable dt = this.DataSource;
			DataRow[] rows = dt.Select(String.Format("{0} = '{1}'", LinkerControl.IDColumnName, e.CommandArgument));

			if (rows != null && rows.Length > 0)
			{
				DataRow dr = rows[0];
				dr.Delete();
			}

			dt.AcceptChanges();

			this.DataSource = dt;
			this.DataBind();
		}

		private void PostBackHandler_PostBack(object sender, DNNContentBuilder.UI.WebControls.PostBackEventArgs e)
		{
			string key = "ERR_NO_LINKER_DICTIONARY";
			object savedState = this.Session[FormEngineChildControl.SessionKey];

			// Check that the name/value dictionary has been set
			ArgumentValidator.AssertNotNull(savedState, key);
			ArgumentValidator.AssertExpectedType(savedState, typeof(FieldDefinitionList), key);

			FieldDefinitionList container = (FieldDefinitionList)savedState;
			
			this.Session.Remove(FormEngineChildControl.SessionKey);

			DataTable dt = this.DataSource;

			// Perform a shallow copy of the session contents
			this.DataSource = new Adapter(container).Merge(dt);
			this.DataBind();
		}
		#endregion

		#region Implementation

		public void DataBind(System.Guid foreignKeyValue)
		{
			this.ForeignKeyValue = foreignKeyValue.ToString();
			
			this.LoadData();
			this.DataBind();
		}

		/// <summary>
		/// Overrides <see cref="Control.DataBind()"/>
		/// </summary>
		public override void DataBind()
		{
			dlCustomTable.DataSource = this.DataSource;
			dlCustomTable.DataBind();
		}

		/// <summary>
		/// Wrapper for <see cref="ContextUtil.GetModuleId"/>
		/// </summary>
		/// <returns></returns>
		public string GetModuleId()
		{
			return ContextUtil.GetModuleId(this).ToString();
		}

		/// <summary>
		/// Wrapper for <see cref="ContextUtil.GetTabId"/>
		/// </summary>
		/// <returns></returns>
		public string GetTabId()
		{
			return ContextUtil.GetTabId(this).ToString();
		}
		#endregion

		#region Web Form Designer generated code
		override protected void OnInit(EventArgs e)
		{
			//
			// CODEGEN: This call is required by the ASP.NET Web Form Designer.
			//
			InitializeComponent();
			base.OnInit(e);
		}
		
		/// <summary>
		///		Required method for Designer support - do not modify
		///		the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.Load += new System.EventHandler(this.Page_Load);
			this.dlCustomTable.ItemCreated += new System.Web.UI.WebControls.DataListItemEventHandler(this.dlCustomTable_ItemCreated);
			this.PostBackHandler.PostBack += new DNNContentBuilder.UI.WebControls.PostBackEventHandler(this.PostBackHandler_PostBack);

		}
		#endregion

		#region IFormlet Members

		public bool IsValid
		{
			get
			{
				return this.isValid;
			}
		}

		public string GetValue(string xml)
		{
			xmlDoc.LoadXml(xml);

			xmlNode = xmlDoc.SelectSingleNode("//SystemType");
			xmlNode.InnerText = "String";

			xmlNode = xmlDoc.SelectSingleNode("//Value");
			xmlNode.InnerXml = Adapter.GetXml(this.DataSource);

			return xmlDoc.InnerXml;
		}

		public void SetValue(string xml)
		{
			xmlDoc.LoadXml(xml);
			xmlNode = xmlDoc.SelectSingleNode("//Value");
			this.DataSource = Adapter.GetDataTable(xml);
		}

		public void SetConfig(string xml) 
		{
			xmlDoc.LoadXml(xml);
			xmlNode = xmlDoc.SelectSingleNode("//Height");
		}
		#endregion
	}
}
